Use dataSource.transaction() with a callback for automatic commit and rollback — the recommended approach for most cases. Use QueryRunner for manual control when you need savepoints, multiple transaction boundaries, or to pass the transaction context explicitly across method boundaries.
dataSource.transaction() — preferred for most cases; handles commit and rollback automatically.
QueryRunner — use when you need savepoints, multiple nested transactions, or manual commit timing.
Always call qr.release() in a finally block — failing to release leaks connections from the pool.
Never use injected Repository instances inside a transaction — use manager.getRepository(Entity) instead.
Inject DataSource directly into the service; it is always available from the TypeORM module.